﻿// Copyright 2010 Maxime Serrano <lastchronicler@gmail.com>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Drawing;
using NBT;

namespace ForgeCraft {
    public class World {
        public Random rand = new Random();
        public string name = null;
        public const string environmentFileName = "/level.dat";

        public bool snowCovered = false;
        public bool flatgrass = false;
        public Position spawn = new Position(0, 120, 0);
        public long randomSeed = new Random().Next();
        public long time = 4000;
        public byte seaLevel = 50;
        public byte oreDensity = 54;
        public bool playerHealth = true;

        internal BinaryTag level;

        // World Properties
        #region World Properties

        public int redstoneSet = 0;
        public int physicsSet = 0;
        public bool playerDamage = true;
        public bool fallDamage = false;
        public bool enemyMobs = false;
        public bool passiveMobs = false;
        public bool hasBorder = false;
        public Pair<int, int> boorderXY = new Pair<int, int>(0, 0);

        public bool creative = false;
        public bool furnaces = true;

        #endregion

        public List<Player> playerList = new List<Player>();
        public List<MobEntity> mobsList = new List<MobEntity>();
        public List<ObjectEntity> objectsList = new List<ObjectEntity>();

        public static ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
        static object chunkslock = new object();
        static object lightlock = new object();

        internal Dictionary<Point, Chunk> chunks = new Dictionary<Point, Chunk>();

        public Dictionary<BlockPosition, SignInfo> SignList = new Dictionary<BlockPosition, SignInfo>();
        public Dictionary<BlockPosition, Window> WindowList = new Dictionary<BlockPosition, Window>();

        #region Loading

        public bool LoadWorld(string foldername) {
            name = foldername;

            SignDB.Load(this);
            WindowDB.Load(this);

            Directory.CreateDirectory(name);
            if (File.Exists(foldername + environmentFileName)) {
                locker.EnterWriteLock();
                try {
                    using (FileStream fs = File.OpenRead(foldername + environmentFileName)) {
                        using (GZipStream gs = new GZipStream(fs, CompressionMode.Decompress)) {
                            level = NbtParser.ParseTagStream(gs);
                        }
                    }
                    spawn.x = (double)level["Data"]["SpawnX"].GetInt();
                    spawn.y = (double)level["Data"]["SpawnY"].GetInt();
                    spawn.z = (double)level["Data"]["SpawnZ"].GetInt();
                    spawn.stance = spawn.y - 1.5;
                    //snowCovered = (level["Data"]["SnowCovered"].GetByte() > 0);
                    //flatgrass = (level["Data"]["FlatGrass"].GetByte() > 0);
                    time = level["Data"]["Time"].GetLong();
                    randomSeed = level["Data"]["RandomSeed"].GetLong();
                    //seaLevel = level["Data"]["SeaLevel"].GetByte();
                    //oreDensity = level["Data"]["OreDensity"].GetByte();
                    //nextID = (ushort)level["Data"]["NextID"].GetShort();
                    //freeIDs.Clear();
                    //foreach (BinaryTag freeID in (BinaryTag[])level["Data"]["FreeIDs"].Payload)
                        //freeIDs.Enqueue((ushort)freeID.Payload);

                    return true;
                } catch (Exception e) {
                    Logger.Log("World.LoadWorld: Failed to load environment file: {0}", LogType.Error, e);
                    return false;
                } finally {
                    locker.ExitWriteLock();
                }
            }
            Logger.Log("Failed to find world environment file.", LogType.Warning);
            spawn.x = 0;
            spawn.y = 120;
            spawn.z = 0;
            spawn.stance = spawn.y - 1.5;
            snowCovered = false;
            time = 4000;
            randomSeed = ((rand.Next() << 32) | rand.Next());
            level = null;
            oreDensity = 24;
            SaveWorld();
            return true;
        }

        #endregion

        #region Saving

        public bool SaveWorld() {
            if (name == null)
                return false;
            return SaveWorld(name);
        }

        public bool SaveWorld(string foldername) {

            SignDB.Save(this);
            WindowDB.Save(this);

            if (File.Exists(foldername + environmentFileName)) {
                locker.EnterWriteLock();
                try {
                    BinaryTag[] FreeIDList = new BinaryTag[Server.freeIDs.Count()];
                    for (int i = 0; i < Server.freeIDs.Count(); i++)
                        FreeIDList[i] = new BinaryTag(TagType.Short, (short)Server.freeIDs.ElementAt(i));

                    level["Data"]["SpawnX"].Payload = (int)spawn.x;
                    level["Data"]["SpawnY"].Payload = (int)spawn.y;
                    level["Data"]["SpawnZ"].Payload = (int)spawn.z;
                    level["Data"]["Time"].Payload = (long)time;
                    level["Data"]["RandomSeed"].Payload = (long)randomSeed;
                    //level["Data"]["SnowCovered"].Payload = (byte)(snowCovered ? 1 : 0);
                    //level["Data"]["FlatGrass"].Payload = (byte)(flatgrass ? 1 : 0);
                    //level["Data"]["SeaLevel"].Payload = seaLevel;
                    //level["Data"]["OreDensity"].Payload = oreDensity;
                    //level["Data"]["NextID"].Payload = (short)nextID;
                    //level["Data"]["FreeIDs"].Payload = FreeIDList;
                    using (FileStream fs = File.OpenWrite(foldername + environmentFileName)) {
                        using (GZipStream gs = new GZipStream(fs, CompressionMode.Compress)) {
                            NbtWriter.WriteTagStream(level, gs);
                        }
                    }
                } catch (Exception e) {
                    Logger.Log("World.LoadWorld: Failed to save environment file: {0}", LogType.Error, e);
                    return false;
                } finally {
                    locker.ExitWriteLock();
                }
            } else {
                Logger.Log("Generating new environment file ({0})", LogType.Warning, foldername + environmentFileName);

                BinaryTag[] FreeIDList = new BinaryTag[Server.freeIDs.Count()];
                for (int i = 0; i < Server.freeIDs.Count(); i++)
                    FreeIDList[i] = new BinaryTag(TagType.Short, (short)Server.freeIDs.ElementAt(i));

                BinaryTag[] DataTag = new BinaryTag[5];
                DataTag[0] = new BinaryTag(TagType.Long, (long)time);
                DataTag[0].Name = "Time";
                DataTag[1] = new BinaryTag(TagType.Int, (int)spawn.x);
                DataTag[1].Name = "SpawnX";
                DataTag[2] = new BinaryTag(TagType.Int, (int)spawn.y);
                DataTag[2].Name = "SpawnY";
                DataTag[3] = new BinaryTag(TagType.Int, (int)spawn.z);
                DataTag[3].Name = "SpawnZ";
                DataTag[4] = new BinaryTag(TagType.Long, (long)randomSeed);
                DataTag[4].Name = "RandomSeed";

                BinaryTag[] RootTag = new BinaryTag[1];
                RootTag[0] = new BinaryTag(TagType.Compound, DataTag);
                RootTag[0].Name = "Data";

                level = new BinaryTag(TagType.Compound, RootTag);
                locker.EnterWriteLock();
                try {
                    using (FileStream fs = File.OpenWrite(foldername + environmentFileName)) {
                        using (GZipStream gs = new GZipStream(fs, CompressionMode.Compress)) {
                            NbtWriter.WriteTagStream(level, gs);
                        }
                    }
                } catch (Exception e) {
                    Logger.Log("World.LoadWorld: Failed to save new environment file: {0}", LogType.Error, e);
                    return false;
                } finally {
                    locker.ExitWriteLock();
                }

                return true;
            }
            //TODO: Why is this in twice??
            lock (chunkslock) {
                lock (chunkslock) {
                    foreach (Chunk chunk in chunks.Values.ToArray()) {
                        if (chunk.hasChanged)
                        {
                            chunk.Save();
                        }
                        if (chunk.players.Count() == 0 && (chunk.UseCount <= 0))
                        {
                            UnloadChunk(chunk);
                        }
                    }
                }
            }
            return true;
        }

        #endregion

        #region Lighting

        public void ChangeLight(byte lightValue, int x, byte y, int z) {
            lock (lightlock) {
                Logger.Log("Calculating lighting for block ({0}, {1}, {2})", LogType.Debug, x, y, z);

                byte OldLight = GetBlock(x, y, z).lighting;

                if (lightValue <= OldLight) {
                    DefloodLight(GetBlock(x, y, z).lighting, x, y, z);
                    FloodLight(lightValue, x, y, z);
                } else if (lightValue > OldLight)
                    FloodLight(lightValue, x, y, z);
            }
        }

        public void FloodLight(byte lightValue, int x, byte y, int z) {
            if (y > 127 || y < 0) return;
            if(lightValue > 0x0F || lightValue < 0x02) return;
            ChunkBlock curBlock = GetBlock(x, y, z);
            if (!Blocks.LightCarryingBlocks.Contains((Block)curBlock.type)) return;
            curBlock.lighting = lightValue;
            if (lightValue == 1)
                curBlock.type = (byte)Block.RedMushroom;
            SetBlock(curBlock, x, y, z);
            if (lightValue < 2) return;

            if (GetBlock((int)(x + 1), (byte)(y + 0), (int)(z + 0)).lighting < (lightValue - 1))
                FloodLight((byte)(lightValue - 1), (int)(x + 1), (byte)(y + 0), (int)(z + 0));

            if (GetBlock((int)(x - 1), (byte)(y - 0), (int)(z - 0)).lighting < (lightValue - 1))
                FloodLight((byte)(lightValue - 1), (int)(x - 1), (byte)(y - 0), (int)(z - 0));

            if (GetBlock((int)(x + 0), (byte)(y + 1), (int)(z + 0)).lighting < (lightValue - 1))
                FloodLight((byte)(lightValue - 1), (int)(x + 0), (byte)(y + 1), (int)(z + 0));

            if (GetBlock((int)(x - 0), (byte)(y - 1), (int)(z - 0)).lighting < (lightValue - 1))
                FloodLight((byte)(lightValue - 1), (int)(x - 0), (byte)(y - 1), (int)(z - 0));

            if (GetBlock((int)(x + 0), (byte)(y + 0), (int)(z + 1)).lighting < (lightValue - 1))
                FloodLight((byte)(lightValue - 1), (int)(x + 0), (byte)(y + 0), (int)(z + 1));

            if (GetBlock((int)(x - 0), (byte)(y - 0), (int)(z - 1)).lighting < (lightValue - 1))
                FloodLight((byte)(lightValue - 1), (int)(x - 0), (byte)(y - 0), (int)(z - 1));
        }

        void DefloodLight(byte lightValue, int x, byte y, int z) {
            List<BlockPosition> ReFlood = new List<BlockPosition>();

            // Flood 0x00 for light
            DefloodLightBlock(lightValue, x, y, z, ref ReFlood);

            // Re-flood light where there was light from another lightsource detected
            foreach (BlockPosition block in ReFlood)
                FloodLight(GetBlock(block.x, block.y, block.z).lighting, block.x, block.y, block.z);

            ReFlood.Clear();
            ReFlood = null;
        }

        void DefloodLightBlock(byte lightValue, int x, byte y, int z, ref List<BlockPosition> ReFlood) {
            if (y > 127 || y < 0) return;
            if (lightValue > 0x0F || lightValue < 0x00) return;
            ChunkBlock curBlock = GetBlock(x, y, z);
            if (curBlock.lighting == 0) return;
            if (!Blocks.LightCarryingBlocks.Contains((Block)curBlock.type)) return;
            curBlock.lighting = 0;
            SetBlock(curBlock, x, y, z);
            if (lightValue < 0) return;

            if (GetBlock((int)(x + 1), (byte)(y + 0), (int)(z + 0)).lighting <= lightValue)
                DefloodLightBlock((byte)(lightValue - 1), (int)(x + 1), (byte)(y + 0), (int)(z + 0), ref ReFlood);
            else
                ReFlood.Add(new BlockPosition((int)(x + 1), (byte)(y + 0), (int)(z + 0)));

            if (GetBlock((int)(x - 1), (byte)(y - 0), (int)(z - 0)).lighting <= lightValue)
                DefloodLightBlock((byte)(lightValue - 1), (int)(x - 1), (byte)(y - 0), (int)(z - 0), ref ReFlood);
            else
                ReFlood.Add(new BlockPosition((int)(x - 1), (byte)(y - 0), (int)(z - 0)));

            if (GetBlock((int)(x + 0), (byte)(y + 1), (int)(z + 0)).lighting <= lightValue)
                DefloodLightBlock((byte)(lightValue - 1), (int)(x + 0), (byte)(y + 1), (int)(z + 0), ref ReFlood);
            else
                ReFlood.Add(new BlockPosition((int)(x + 0), (byte)(y + 1), (int)(z + 0)));

            if (GetBlock((int)(x - 0), (byte)(y - 1), (int)(z - 0)).lighting <= lightValue)
                DefloodLightBlock((byte)(lightValue - 1), (int)(x - 0), (byte)(y - 1), (int)(z - 0), ref ReFlood);
            else
                ReFlood.Add(new BlockPosition((int)(x - 0), (byte)(y - 1), (int)(z - 0)));

            if (GetBlock((int)(x + 0), (byte)(y + 0), (int)(z + 1)).lighting <= lightValue)
                DefloodLightBlock((byte)(lightValue - 1), (int)(x + 0), (byte)(y + 0), (int)(z + 1), ref ReFlood);
            else
                ReFlood.Add(new BlockPosition((int)(x + 0), (byte)(y + 0), (int)(z + 1)));

            if (GetBlock((int)(x - 0), (byte)(y - 0), (int)(z - 1)).lighting <= lightValue)
                DefloodLightBlock((byte)(lightValue - 1), (int)(x - 0), (byte)(y - 0), (int)(z - 1), ref ReFlood);
            else
                ReFlood.Add(new BlockPosition((int)(x - 0), (byte)(y - 0), (int)(z - 1)));
        }

        #endregion

        #region Chunks

        internal Chunk GetChunk(int x, int z) {
            lock (chunkslock) {
                Chunk returnChunk = null;
                if (chunks.TryGetValue(new Point(x, z), out returnChunk))
                    return returnChunk;
                chunks.Add(new Point(x, z), new Chunk(x, z, true, this));
                if (chunks.TryGetValue(new Point(x, z), out returnChunk))
                    return returnChunk;
                return null;
            }
        }

        internal void UnloadChunk(Chunk chunk) {
            if (chunk.hasChanged)
                chunk.Save();
            chunks.Remove(new Point(chunk.x, chunk.z));
        }

        #endregion

        #region Blocks

        public ChunkBlock GetBlock(int x, byte y, int z) {
            return GetBlock(x, y, z, true);
        }

        public ChunkBlock GetBlock(int x, byte y, int z, bool loadIfNeeded) {
            if (y > 127 || y < 0)
                return new ChunkBlock(Block.Air, 0, 0);

            lock (chunkslock) {
                int chunkX = x / 16;
                int chunkZ = z / 16;
                x = (x % 16);
                z = (z % 16);
                if (x < 0 && Math.Abs(x) != 0) chunkX--;
                if (z < 0 && Math.Abs(z) != 0) chunkZ--;
                Chunk chunk = null;
                if (loadIfNeeded)
                    chunk = GetChunk(chunkX, chunkZ);
                else
                    if (!(chunks.TryGetValue(new Point(x, z), out chunk)))
                        return new ChunkBlock(Block.Air, 0, 0);
                if (x < 0) x = Math.Abs(x + 16);
                if (z < 0) z = Math.Abs(z + 16);
                return chunk.GetBlock((byte)x, y, (byte)z);
            }
        }

        public void SetBlock(ChunkBlock block, int x, byte y, int z) {
            SetBlock(block, x, y, z, false);
        }

        public void SetBlock(ChunkBlock block, int x, byte y, int z, bool calculateLight) {
            lock (chunkslock) {
                int chunkX = x / 16;
                int chunkZ = z / 16;
                x = (x % 16);
                z = (z % 16);
                if (x < 0 && Math.Abs(x) != 0) chunkX--;
                if (z < 0 && Math.Abs(z) != 0) chunkZ--;
                Chunk chunk = GetChunk(chunkX, chunkZ);
                if (chunk == null) return;
                if (x < 0) x = Math.Abs(x + 16);
                if (z < 0) z = Math.Abs(z + 16);
                chunk.SetBlock(block, (byte)x, y, (byte)z, calculateLight);
            }
        }

        //Changes a block and then sends it to the required players.
        public void ChangeBlock(ChunkBlock block, int x, byte y, int z, Player player, bool skipPhysics = false)
        {
            if (player != null)
            {
                //p.CurrentChunk.hasChanged = true;
            }

            SetBlock(block, x, y, z, true);
            SendToPlayersInRange(PacketWriter.MakeBlockChange(block, x, y, z), new Position(x, y, z));

            if (!skipPhysics)
            {
                if (block.type == 0) 
                {
                    PAddCheck(new BlockPosition(x, (byte)(y + 1), z), new int[8]);
                    PAddCheck(new BlockPosition(x + 1, y, z), new int[8]);
                    PAddCheck(new BlockPosition(x - 1, y, z), new int[8]);
                    PAddCheck(new BlockPosition(x, y, z + 1), new int[8]);
                    PAddCheck(new BlockPosition(x, y, z - 1), new int[8]);
                }

                if (Blocks.PhysicsBlocks.Contains((Block)block.type))
                {
                    PAddCheck(new BlockPosition(x, y, z), block, new int[8]);

                    PAddCheck(new BlockPosition(x, (byte)(y + 1), z), new int[8]);
                    PAddCheck(new BlockPosition(x + 1, y, z), new int[8]);
                    PAddCheck(new BlockPosition(x - 1, y, z), new int[8]);
                    PAddCheck(new BlockPosition(x, y, z + 1), new int[8]);
                    PAddCheck(new BlockPosition(x, y, z - 1), new int[8]);
                }

                if (Blocks.RedstoneReceptor.Contains((Block)block.type) || Blocks.RedstoneActivator.Contains((Block)block.type))
                {
                    RAddCheck(new BlockPosition(x, y, z), block, new int[8]);
                }
            }
        }


        public void BlastBlock(int x, byte y, int z, int maxResistance, int damage, int chance)
        {
            if (rand.Next(0, chance) != 0) return;
            // Get Block hardness
            byte resistance = 255;

            ChunkBlock cb = GetBlock(x, y, z);

            Blocks.BlastResistance.TryGetValue((Block)cb.type, out resistance);

            if (resistance < maxResistance)
            {
                if ((Block)cb.type == Block.TNT)
                {
                    ChangeBlock(new ChunkBlock(Block.Air, 0, 0), x, y, z, null, true);
                    new ObjectEntity(x, y, z, new ObjectItem((byte)Object.ActivatedTNT), this);
                    ExplodeTNT(x, y, z, 1);
                }
                else
                {
                    ChangeBlock(new ChunkBlock(Block.Air, 0, 0), x, y, z, null, true);
                    if (rand.Next(0, 10) > 7)
                    {
                        // TODO: This will spawn raw blocks, which we don't really need. Will fix with master (configurable) item txt.
                        //new PickupEntity(x, y, z, new InventoryItem((short)cb.type, 1, 0), this);
                    }
                }
            }

            playerList.ForEach(delegate(Player p)
            {
                if ((int)p.pos.x == x && (int)p.pos.y == y && (int)p.pos.z == z)
                {
                    p.Damage(damage, "tnt");
                }
            });


        }

        public void ExplodeTNT(int x, byte y, int z, int size)
        {
            int xx, zz; Random rand = new Random(); byte yy;

            //AddUpdate(PosToInt(x, y, z), Block.tntexplosion, true);

            for (xx = (x - (size + 1)); xx <= (x + (size + 1)); ++xx)
                for (yy = (byte)(y - (size + 1)); yy <= (y + (size + 1)); ++yy)
                    for (zz = (z - (size + 1)); zz <= (z + (size + 1)); ++zz)
                    {
                            BlastBlock(xx, yy, zz, 250, 10, 0);
                    }

            for (xx = (x - (size + 2)); xx <= (x + (size + 2)); ++xx)
                for (yy = (byte)(y - (size + 2)); yy <= (y + (size + 2)); ++yy)
                    for (zz = (z - (size + 2)); zz <= (z + (size + 2)); ++zz)
                    {
                        BlastBlock(xx, yy, zz, 30, 6, 2);
                    }

            for (xx = (x - (size + 3)); xx <= (x + (size + 3)); ++xx)
                for (yy = (byte)(y - (size + 3)); yy <= (y + (size + 3)); ++yy)
                    for (zz = (z - (size + 3)); zz <= (z + (size + 3)); ++zz)
                    {
                        BlastBlock(xx, yy, zz, 20, 4, 4);
                    }

            for (xx = (x - (size + 4)); xx <= (x + (size + 4)); ++xx)
                for (yy = (byte)(y - (size + 4)); yy <= (y + (size + 4)); ++yy)
                    for (zz = (z - (size + 4)); zz <= (z + (size + 4)); ++zz)
                    {
                        BlastBlock(xx, yy, zz, 10, 2, 6);
                    }
        }

        #endregion

        #region Entities

        public void Update()
        {
            
            List<Entity> entities = new List<Entity>();

            // TODO: CITICAL!! This occasioanlly crashes to the main loop - due to locking other chunks for loading by player.
            // ...Another reason for one central queue that loads and unloads chunks for ALL the players.
            
            foreach (KeyValuePair<Point, Chunk> kvp in new Dictionary<Point, Chunk>(chunks))
            {
                kvp.Value.Entities.ForEach(delegate (Entity e)
                {
                    entities.Add(e);
                });
            }

            // Update entities as needed
            entities.ForEach(delegate (Entity e)
            {
                e.Update();
            });

            foreach (MobEntity m in mobsList)
            {
                if (!m.Spawned)
                {
                    // TODO: This needs to be on some kind of timer
                    // Bodies are removed before they settle
                    m.Despawn();
                    mobsList.Remove(m);
                }   
            }
        }

        #endregion

        #region Messages

        // Send to all others in chunk range
        public void SendToOthersInRange(Packet packet, Player player)
        {
            foreach (Player ply in playerList)
            {
                if (ply == player)
                    continue;
                if (!CommonChunks(ply, player))
                    continue;
                ply.Send(packet);
            }
        }

        // Send to all players in chunk range
        public void SendToPlayersInRange(Packet packet, Position pos)
        {
            foreach (Player ply in playerList)
            {
                if (!IsInPlayersChunk(ply, pos))
                    continue;
                ply.Send(packet);
            }
        }

        // Send to all in chunk range (including self)
        public void SendToAllInRange(Packet packet, Player player)
        {
            foreach (Player ply in playerList)
            {
                if (!CommonChunks(ply, player))
                    continue;
                ply.Send(packet);
            }
        }

        // Check if players are in same loaded chunk range
        public static bool CommonChunks(Player ply1, Player ply2)
        {
            /*if ((ply1.LoadedChunksMinX + ply1.ChunkAreaPlayer) <= ply2.LoadedChunksMinX || ply1.currentWorld != ply2.currentWorld)
                return false;
            if (ply1.LoadedChunksMinX >= (ply2.LoadedChunksMinX + ply2.ChunkAreaPlayer) || ply1.currentWorld != ply2.currentWorld)
                return false;
            if ((ply1.LoadedChunksMinZ + ply1.ChunkAreaPlayer) <= ply2.LoadedChunksMinZ || ply1.currentWorld != ply2.currentWorld)
                return false;
            if (ply2.LoadedChunksMinZ >= (ply2.LoadedChunksMinZ + ply2.ChunkAreaPlayer) || ply1.currentWorld != ply2.currentWorld)
                return false;
            return true;*/

            //Alternate method?
            if (ply1.currentWorld == ply2.currentWorld)
            {
                foreach (Chunk c1 in ply1.VisibleChunks)
                {
                    foreach (Chunk c2 in ply2.VisibleChunks)
                    {
                        if (c1.x == c2.x && c1.z == c2.z)
                        {
                            //Logger.Log("Players share Chunks!", LogType.Debug);
                            return true;
                        }
                    }
                }
            }
            //Logger.Log("Players are not sharing Chunks!", LogType.Debug);
            return false;
        }

        // Check if positon is in a loaded chunk range
        public bool IsInPlayersChunk(Player pl, Position pos)
        {
            foreach (Chunk c in pl.VisibleChunks)
            {
                //Logger.Log("Checking: " + c.x + " " + c.z + " vs " + (int)((int)pos.x / 16) + " " + (int)((int)pos.z / 16), LogType.Debug);
                if (c.x == (int)((int)pos.x / 16) && c.z == (int)((int)pos.z / 16))
                {
                    return true;
                }
            }
            return false;
        }

        #endregion

        #region Constructors

        // Physics int[] array
        // Controls the block physics properties
        // 0 - Block Sleep Time
        // 1 - Block Liquid Spread

        // Redstone int[] array
        // Controls the block redstone properties
        // 0 - Block Power Strength up to 15
        // 1 - Block status is currently On (1) or Off (0)
        // 2 - Block status should be turned On (1) or Off (2)
        // 3 - Block status should be toggled true (1) or false (0)

        

        public class CheckBlock
        {
            public BlockPosition ps;
            public ChunkBlock b;
            public short time;
            public int[] extraProperties;
            public CheckBlock(BlockPosition bps, ChunkBlock cb, int[] binfo)
            {
                ps = bps;
                b = cb;
                time = 0;
                extraProperties = binfo;
            }
        }
        //-------------------------------------------------------------------------------------------------------------------------------------------------------
        public class UpdateBlock
        {
            public BlockPosition ps;
            public ChunkBlock b;
            public byte originalType;
            public int[] extraProperties;
            public UpdateBlock(BlockPosition bps, ChunkBlock cb, byte btype, int[] binfo)
            {
                ps = bps;
                b = cb;
                originalType = btype;
                extraProperties = binfo;
            }
        }

        public void Blockchange(BlockPosition ps, ChunkBlock b, byte t)    //Block change made by physics
        {
            if (ps.y < 0 || ps.y > 128) return;

            //Sometimes you may want to know what the previous block was?
            b.type = t;

            Logger.Log("Sending Changes for:" + ps.x + " " + ps.y + " " + ps.z, LogType.Debug);
            ChangeBlock(b, ps.x, ps.y, ps.z, null);
        }

        #endregion

        #region Furnaces

        // Update Furnaces (windowss)
        public void UpdateFurnace()
        {
            //TODO: Add support for variable fire and progress depending on items.
            short progress = 0;
            short fire = 0;
            short smelting = 0;

            foreach (KeyValuePair<BlockPosition, Window> kvpds in WindowList)
            {
                try
                {
                    if (kvpds.Value.Type == 2) //Is a furnace
                    {
                        string[] furnaceProgress = kvpds.Value.additional.Split(':');

                        short.TryParse(furnaceProgress[0], out progress);
                        short.TryParse(furnaceProgress[1], out fire);
                        short.TryParse(furnaceProgress[2], out smelting);

                        short startFire = fire;
                        short startProgress = progress;

                        ChunkBlock cb = GetBlock(kvpds.Key.x, kvpds.Key.y, kvpds.Key.z);

                        if (fire == 0 && (Blocks.SmeltableBlocks.Contains((Block)kvpds.Value.slots[0].Type) || progress > 0 || Items.SmeltableItems.Contains((Item)kvpds.Value.slots[0].Type))) //Get Fuel
                        {
                            if ((Block)cb.type == Block.BurningFurnace)
                            {
                                cb.type = (byte)Block.Furnace;
                                ChangeBlock(cb, kvpds.Key.x, kvpds.Key.y, kvpds.Key.z, null);
                            }

                            if ((Item)kvpds.Value.slots[1].Type == Item.Coal)
                            {
                                kvpds.Value.slots[1] = kvpds.Value.PopItem(kvpds.Value.slots[1]);
                                kvpds.Value.SetSlot(1, kvpds.Value.slots[1]);
                                fire = 256; //Each coal item gives 16 uses per item
                            }
                            else if ((Block)kvpds.Value.slots[1].Type == Block.Wood)
                            {
                                kvpds.Value.slots[1] = kvpds.Value.PopItem(kvpds.Value.slots[1]);
                                kvpds.Value.SetSlot(1, kvpds.Value.slots[1]);
                                fire = 128; //Each wood item gives 8 uses per item
                            }
                            else if (progress > 0)
                            {
                                //The item is LOST if the furnace runs out of fuel!
                                progress = 0;
                                smelting = 0;
                            }
                        }

                        if (fire > 0) //Furnace is lit
                        {
                            if ((Block)cb.type == Block.Furnace)
                            {
                                cb.type = (byte)Block.BurningFurnace;
                                ChangeBlock(cb, kvpds.Key.x, kvpds.Key.y, kvpds.Key.z, null);
                            }
                            fire -= 8;

                            if (progress == 0 && (Blocks.SmeltableBlocks.Contains((Block)kvpds.Value.slots[0].Type) || Items.SmeltableItems.Contains((Item)kvpds.Value.slots[0].Type))) //Get next item to smelt.
                            {
                                smelting = kvpds.Value.slots[0].Type;
                                kvpds.Value.slots[0] = kvpds.Value.PopItem(kvpds.Value.slots[0]);
                                kvpds.Value.SetSlot(0, kvpds.Value.slots[0]);
                                progress += 16;
                            }

                            if (progress > 0 && smelting != 0) //Is smelting something currently
                            {
                                if (progress >= 176) //finished smelting
                                {
                                    InventoryItem i = new InventoryItem(1, 1, 0);

                                    progress = 0;

                                    switch (smelting)
                                    {
                                        // Blocks
                                        case (short)Block.Sand:
                                            i.Type = (short)Block.Glass;
                                            break;
                                        case (short)Block.Cobblestone:
                                            i.Type = (short)Block.Stone;
                                            break;
                                        case (short)Block.MossyCobblestone:
                                            i.Type = (short)Block.Stone;
                                            break;
                                        case (short)Block.CoalOre:
                                            i.Type = (short)Item.Coal;
                                            break;
                                        case (short)Block.IronOre:
                                            i.Type = (short)Item.Iron;
                                            break;
                                        case (short)Block.GoldOre:
                                            i.Type = (short)Item.Gold;
                                            break;
                                        case (short)Block.DiamondOre:
                                            i.Type = (short)Item.Diamond;
                                            break;
                                        case (short)Block.RedstoneOre:
                                            i.Type = (short)Item.RedstoneDust;
                                            break;
                                        case (short)Block.LapisOre:
                                            i.Type = (short)Block.LapisBlock;
                                            break;

                                        // Itmes
                                        case (short)Item.Pork:
                                            i.Type = (short)Item.GrilledPork;
                                            break;
                                        case (short)Item.RawFish:
                                            i.Type = (short)Item.CookedFish;
                                            break;
                                        case (short)Item.Clay:
                                            i.Type = (short)Item.ClayBrick;
                                            break;
                                    }

                                    if (kvpds.Value.slots[2].Count < 64)
                                    {
                                        Pair<bool, InventoryItem> result = kvpds.Value.TryAddItem(2, i);

                                        if (!result.First)
                                        {
                                            //TODO: Need some handling here!!
                                        }

                                        kvpds.Value.SetSlot(2, kvpds.Value.slots[2]);
                                    }

                                    smelting = 0;
                                }
                                else //Update smelting counter and wait
                                {
                                    progress += 16;
                                }
                            }
                        }
                        else
                        {
                            if ((Block)cb.type == Block.BurningFurnace)
                            {
                                cb.type = (byte)Block.Furnace;
                                ChangeBlock(cb, kvpds.Key.x, kvpds.Key.y, kvpds.Key.z, null);
                            }
                        }

                        kvpds.Value.additional = progress + ":" + fire + ":" + smelting;

                        //Finally update any viewing players.
                        if (startProgress != progress)
                        {
                            kvpds.Value.UpdateViewers(0);
                        }

                        if (startFire != fire && fire % 16 == 0)
                        {
                            kvpds.Value.UpdateViewers(1);
                        }
                    }
                }
                catch (Exception e)
                {
                    Logger.Log("Error in " + this.name + "_UpdateFurnaces: {0} {1} Exception: {2}", LogType.Error, kvpds.Value.ID, kvpds.Value.additional, e);
                }
            }

        }

        #endregion

        #region RedStone

        public Thread redsThread;

        
        public int maxRange = 16;

        public int rlastCheck = 0;
        public int rlastUpdate = 0;

        List<CheckBlock> redstoneListCheck = new List<CheckBlock>();  //A list of blocks that need to be updated
        List<UpdateBlock> redstoneListUpdate = new List<UpdateBlock>();  //A list of block to change after calculation

        public void Redstone()
        {
            int wait = 2000;
            while (true)
            {
                try
                {
                retry: if (wait > 0) Thread.Sleep(wait);
                    if (redstoneSet == 0 || redstoneListCheck.Count == 0) goto retry;
                    if (redstoneSet > 0) CalcRedstone();
                }
                catch
                {
                    Logger.Log("Caught Exception in Redstone", LogType.Debug);
                }
            }
        }

        public void CalcRedstone()
        {
            int xx, zz;
            byte yy;

            if (redstoneSet > 0)
            {
                Random rand = new Random();
                //Logger.Log("Last Count: " + lastCheck, LogType.Debug);
                
                rlastCheck = redstoneListCheck.Count;

                redstoneListCheck.ForEach(delegate(CheckBlock C)
                {
                    C.time++;

                    if (Blocks.RedstoneActivator.Contains((Block)C.b.type)) //Activators provide power to surrounding blocks
                    {
                        switch ((Block)C.b.type)
                        {
                            case Block.Lever:
                            case Block.StoneButton: // TODO: Needs to be some kind of timer?
                                if (C.b.metadata == 8) // Lever is providing power
                                {
                                    C.extraProperties[0] = 15; // Set power level
                                    C.extraProperties[2] = 1; // Turn master on

                                    RCheckXY(C.ps.x, C.ps.y, C.ps.z, new int[8] { 14, 0, 1, 0, 0, 0, 0, 0 }); // Turn on any blocks nearby
                                }
                                else // Lever is no longer providing power
                                {
                                    C.extraProperties[0] = 0; // Set power level
                                    C.extraProperties[2] = 0; // Turn master off

                                    RCheckXY(C.ps.x, C.ps.y, C.ps.z, new int[8] { 0, 0, 2, 0, 0, 0, 0, 0 }); // Turn off any blocks nearby
                                }
                                break;
                            case Block.StonePressurePlate:
                            case Block.WoodenPressurePlate:
                                if (C.b.metadata == 1) // Plate is providing power
                                {
                                    C.extraProperties[0] = 15; // Set power level
                                    C.extraProperties[2] = 1; // Turn master on

                                    RCheckXY(C.ps.x, C.ps.y, C.ps.z, new int[8] { 14, 0, 1, 0, 0, 0, 0, 0 }); // Turn on any blocks nearby
                                }
                                else // Plate is not providing power
                                {
                                    C.extraProperties[0] = 0; // Set power level
                                    C.extraProperties[2] = 0; // Turn master off

                                    RCheckXY(C.ps.x, C.ps.y, C.ps.z, new int[8] { 0, 0, 2, 0, 0, 0, 0, 0 }); // Turn off any blocks nearby
                                }
                                redstoneListUpdate.Add(new UpdateBlock(C.ps, C.b, C.b.type, C.extraProperties));
                                break;

                            case Block.RepeaterOn:      // Always provides power.
                            case Block.RedstoneTorchOn:
                                C.extraProperties[0] = 15; // Set power level
                                C.extraProperties[2] = 1; // Turn master on

                                C.extraProperties[0]++;

                                xx = C.ps.x; yy = C.ps.y; zz = C.ps.z;

                                RCheckXY(C.ps.x, C.ps.y, C.ps.z, new int[8] { 14, 0, 0, 1, 0, 0, 0, 0 }); // Toggle any blocks nearby

                                redstoneListUpdate.Add(new UpdateBlock(C.ps, C.b, C.b.type, C.extraProperties));
                                break;
                        }

                        redstoneListCheck.Remove(C);
                    }
                    else if (Blocks.RedstoneReceptor.Contains((Block)C.b.type)) // Receptors just take input and rarely update others
                    {
                        if (C.extraProperties[2] == 2) // Turn master off
                        {
                            switch ((Block)C.b.type)
                            {
                                case Block.RedstoneWire:
                                case Block.RedstoneTorchOff:
                                case Block.RepeaterOff:
                                    if (C.extraProperties[0] > 0)
                                    {
                                        C.extraProperties[0] = 0;
                                        C.b.metadata = 0;
                                    }

                                    RCheckXY(C.ps.x, C.ps.y, C.ps.z, new int[8] { 0, 0, 2, 0, 0, 0, 0, 0 }); // Toggle any blocks nearby

                                    redstoneListUpdate.Add(new UpdateBlock(C.ps, C.b, C.b.type, C.extraProperties));
                                    break;
                                case Block.IronDoor:
                                    // TODO: DoorOpen(), DoorClose()
                                    break;

                                case Block.WallSign:
                                case Block.Signpost:
                                    // TODO: Extended features (Gates, Bridges, etc)
                                    break;
                            }
                        }
                        else if (C.extraProperties[2] == 1) // Turn master on
                        {
                            switch ((Block)C.b.type)
                            {
                                case Block.RedstoneWire:
                                case Block.RedstoneTorchOff:
                                case Block.RepeaterOff:

                                    if (C.extraProperties[0] == 0)
                                    {
                                        C.extraProperties[0] = 15;
                                    }

                                    C.b.metadata = (byte)C.extraProperties[0];

                                    RCheckXY(C.ps.x, C.ps.y, C.ps.z, new int[8] { C.extraProperties[0] - 1, 0, 1, 0, 0, 0, 0, 0 }); // Toggle any blocks nearby

                                    redstoneListUpdate.Add(new UpdateBlock(C.ps, C.b, C.b.type, C.extraProperties));
                                    break;

                                case Block.IronDoor:
                                    // TODO: DoorOpen(), DoorClose()
                                    break;

                                case Block.WallSign:
                                case Block.Signpost:
                                    // TODO: Extended features (Gates, Bridges, etc)
                                    break;
                            }
                        }
                        else
                        {
                            redstoneListCheck.Remove(C);
                        }

                        if (C.time > 10)
                        {
                            redstoneListCheck.Remove(C);
                        }
                    }
                });

                redstoneListCheck.RemoveAll(Check => Check.extraProperties[0] > 16);  //Remove all that are finished with 200 time

                rlastUpdate = redstoneListUpdate.Count;

                redstoneListUpdate.ForEach(delegate(UpdateBlock C)
                {
                    Blockchange(C.ps, C.b, C.originalType);
                });

                redstoneListUpdate.Clear();
            }
        }

        public void RCheckXY(int x, byte y, int z, int[] bprop)
        {
            // Checks a 3x3 square along the x axis, ignoring the 
            ChunkBlock Cb = new ChunkBlock();

            int xx = x, zz = z;
            byte yy = y;

            for (xx = x - 1; xx < x + 1; xx++) // Check along one axis (x)
            {
                for (yy = (byte)(y - 1); yy > y + 1; yy++) // Also check above and below blocks
                {
                    if (yy != y) // We don't want to activate the same block again
                    {
                        RAddCheck(new BlockPosition(xx, yy, zz), Cb, bprop, true);
                    }
                }
            }

            for (zz = z - 1; xx < z + 1; zz++) // Check along one axis (z)
            {
                for (yy = (byte)(y - 1); yy > y + 1; yy++) // Also check above and below blocks
                {
                    if (yy != y) // We don't want to activate the same block again
                    {
                        RAddCheck(new BlockPosition(xx, yy, zz), Cb, bprop, true);
                    }
                }
            }
        }

        public void RAddCheck(BlockPosition p, ChunkBlock b, int[] bprop, bool overRide = false)
        {
            if (overRide == true) //We can't rely on b being correct, so reload it.
            {
                b = GetBlock(p.x, p.y, p.z);
            }

            if (!Blocks.RedstoneActivator.Contains((Block)b.type) && !Blocks.RedstoneReceptor.Contains((Block)b.type)) return;

            if (!redstoneListCheck.Exists(Check => Check.ps.x == p.x) || !redstoneListCheck.Exists(Check => Check.ps.z == p.z) || !redstoneListCheck.Exists(Check => Check.ps.y == p.y))
            {
                redstoneListCheck.Add(new CheckBlock(p, b, bprop));    //Adds block to list to be updated
            }
        }

        public void ClearRedstone()
        {
            redstoneListCheck.Clear();
            redstoneListUpdate.Clear();
        }

        #endregion

        #region Physics

        //A list of block to change after calculation
        //internal Dictionary<Position, UpdateBlock> ListUpdate = new Dictionary<Position, UpdateBlock>();

        //A list of blocks that need to be updated
        //internal Dictionary<Position, CheckBlock> ListCheck = new Dictionary<Position, CheckBlock>();

        public Thread physThread;
        //        public System.Timers.Timer physTimer = new System.Timers.Timer(1000);

        public int overload = 1500;

        public byte[] blocks;

        public int plastCheck = 0;
        public int plastUpdate = 0;

        List<CheckBlock> physicsListCheck = new List<CheckBlock>();  //A list of blocks that need to be updated
        List<UpdateBlock> physicsListUpdate = new List<UpdateBlock>();  //A list of block to change after calculation

        public void Physics()
        {
            int wait = 200;

            Logger.Log("Physics Thread is : " + physThread.ThreadState, LogType.SystemActivity);

            while (true)
            {
                try
                {
                retry: if (wait > 0) Thread.Sleep(wait);
                    if (physicsSet == 0 || physicsListCheck.Count == 0) goto retry;
                    if (physicsSet > 0) CalcPhysics();
                }
                catch
                {
                    Logger.Log("Caught Exception in Physics", LogType.Debug);
                    wait = 5000;
                }
            }
        }


        public struct Pos { public int x, z; }

        public void CalcPhysics()
        {
            int xx, zz;
            byte yy;

            if (physicsSet > 0)
            {
                Random rand = new Random();
                

                plastCheck = physicsListCheck.Count;

                Logger.Log("To Process: " + plastCheck, LogType.Debug);

                physicsListCheck.ForEach(delegate(CheckBlock C)
                {
                    C.time++;

                    if (C.extraProperties[0] > 0) //Block is sleeping
                    {
                        C.extraProperties[0]--;
                    }
                    else
                    {
                        if (C.time > 200) return;

                        switch ((Block)C.b.type)
                        {
                            #region Environment

                            case Block.Dirt:
                                //If the light is full, convert the block to grass, then check the surrounding blocks for dirt and spread to them.
                                if (C.b.lighting == 0x0F)
                                {
                                    physicsListUpdate.Add(new UpdateBlock(C.ps, C.b, (byte)(C.b.type - 1), C.extraProperties));

                                    for (xx = C.ps.x - 1; xx <= C.ps.x + 1; xx++)
                                    {
                                        for (yy = (byte)(C.ps.y - 1); yy <= C.ps.y + 1; yy++)
                                        {
                                            for (zz = C.ps.z - 1; zz <= C.ps.z + 1; zz++)
                                            {
                                                //Logger.Log("Checking: " + C.ps.x + " " + C.ps.y + " " + C.ps.z, LogType.Debug);
                                                ChunkBlock cb = GetBlock(xx, (byte)(yy), zz);

                                                if (cb.lighting == 0x0F)
                                                {
                                                    PAddCheck(new BlockPosition(xx, yy, zz), cb, C.extraProperties);
                                                }
                                            }
                                        }
                                    } 
                                }
                                
                                physicsListCheck.Remove(C);
                                break;

                            case Block.Gravel:
                            case Block.Sand:
                                {
                                    ChunkBlock cb = GetBlock(C.ps.x, (byte)(C.ps.y - 1), C.ps.z);

                                    if (cb.type == 0)
                                    {
                                        ChangeBlock(new ChunkBlock(Block.Air, 0, 0), C.ps.x, C.ps.y, C.ps.z, null);
                                        if ((Block)C.b.type == Block.Sand)
                                        {
                                            new ObjectEntity(C.ps.x, C.ps.y, C.ps.z, new ObjectItem((byte)Object.FallingSand), this);
                                        }
                                        else
                                        {
                                            new ObjectEntity(C.ps.x, C.ps.y, C.ps.z, new ObjectItem((byte)Object.FallingGravel), this);
                                        }

                                        System.Timers.Timer waitTimer = new System.Timers.Timer(1000);

                                        waitTimer.Elapsed += delegate
                                        {
                                            waitTimer.Stop();
                                            waitTimer.Dispose();
                                            for (int i = 1; i < 128; i++)
                                            {
                                                cb = GetBlock(C.ps.x, (byte)(C.ps.y - i), C.ps.z);

                                                if (cb.type > 0)
                                                {
                                                    //physicsListUpdate.Add(new UpdateBlock(new BlockPosition(C.ps.x, (byte)(C.ps.y), C.ps.z), , C.b.type, C.extraProperties));
                                                    ChangeBlock(C.b, C.ps.x, (byte)(C.ps.y - (i - 1)), C.ps.z, null);
                                                    //physicsListUpdate.Add(new UpdateBlock(new BlockPosition(C.ps.x, (byte)(C.ps.y - (i - 1)), C.ps.z), C.b, C.b.type, C.extraProperties));
                                                    break;
                                                }
                                            }
                                        };

                                        waitTimer.Start();
                                    }

                                    PAddCheck(new BlockPosition(C.ps.x + 1, C.ps.y, C.ps.z), new int[8]);
                                    PAddCheck(new BlockPosition(C.ps.x - 1, C.ps.y, C.ps.z), new int[8]);
                                    PAddCheck(new BlockPosition(C.ps.x, C.ps.y, C.ps.z + 1), new int[8]);
                                    PAddCheck(new BlockPosition(C.ps.x, C.ps.y, C.ps.z - 1), new int[8]);
                                    PAddCheck(new BlockPosition(C.ps.x, (byte)(C.ps.y + 1), C.ps.z), new int[8]);

                                    physicsListCheck.Remove(C);
                                    break;
                                }

                            case Block.Sponge:
                                // TODO: Sponge Absorb

                                break;

                            #endregion

                            #region Liquids

                            case Block.Water: // Up to 0x07
                            case Block.Lava: // Up to 0x03
                                {
                                    if (C.time % 5 != 0) return;
                                    if (C.b.metadata == 0x08 || C.b.metadata == 0x00)
                                    {
                                        C.time = 250;
                                    }
                                    else
                                    {
                                        C.b.metadata++;
                                    }

                                    if ((Block)C.b.type == Block.Water && C.b.metadata == 8)
                                    {
                                        C.time = 250;
                                        break;
                                    }

                                    if ((Block)C.b.type == Block.Lava && C.b.metadata == 4)
                                    {
                                        C.time = 250;
                                        break;
                                    }

                                    
                                    TryFlood(C.ps.x + 1, C.ps.y, C.ps.z, C.b);
                                    TryFlood(C.ps.x - 1, C.ps.y, C.ps.z, C.b);
                                    TryFlood(C.ps.x, C.ps.y, C.ps.z + 1, C.b);
                                    TryFlood(C.ps.x, C.ps.y, C.ps.z - 1, C.b);
                                    TryFlood(C.ps.x, (byte)(C.ps.y - 1), C.ps.z, C.b, true);

                                    physicsListCheck.Remove(C);
                                }
                                break;

                            #endregion

                            #region Plants

                            case Block.Crops:
                                // Crops grow from 0x0 to 0x7.
                                if (C.b.metadata == 0x7)
                                {
                                    physicsListCheck.Remove(C);
                                    break;
                                }
                                else
                                {
                                    bool hasWater = false;
                                    for (xx = C.ps.x - 3; xx <= C.ps.x + 3; xx++)
                                    {
                                        for (yy = (byte)(C.ps.y - 3); yy <= C.ps.y + 3; yy++)
                                        {
                                            for (zz = C.ps.z - 3; zz <= C.ps.z + 3; zz++)
                                            {
                                                if ((Block)GetBlock(xx, yy, zz).type == Block.Water)
                                                {
                                                    hasWater = true;
                                                }
                                            }
                                        }
                                    }

                                    if (!hasWater)
                                    {
                                        //physicsListUpdate.Add(new UpdateBlock(new BlockPosition(C.ps.x, C.ps.y, C.ps.z), C.b, (byte)Block.Air, C.extraProperties));
                                        physicsListCheck.Remove(C);
                                        break;
                                    }

                                    C.b.metadata++;
                                    ChangeBlock(C.b, C.ps.x, C.ps.y, C.ps.z, null);
                                    C.extraProperties[0] = rand.Next(2, 10);
                                }
                                break;

                            case Block.Reed:
                                // 0x0 is a freshly planted reed. The data value is incremented at random intervals.
                                // When it becomes 15, a new reed block is created on top as long as the total height does not exceed 3. 
                                if (C.b.metadata == 0x15)
                                {
                                    if ((Block)GetBlock(C.ps.x, (byte)(C.ps.y - 2), C.ps.z).type != Block.Reed)
                                    {
                                        if ((Block)GetBlock(C.ps.x, (byte)(C.ps.y + 1), C.ps.z).type == Block.Air)
                                        {
                                            ChangeBlock(new ChunkBlock(C.b.type, 0, C.b.lighting), C.ps.x, (byte)(C.ps.y + 1), C.ps.z, null);
                                        }
                                    }

                                    physicsListCheck.Remove(C);
                                    break;
                                }
                                else
                                {
                                    bool hasWater = false;
                                    for (xx = C.ps.x - 1; xx <= C.ps.x + 1; xx++)
                                    {
                                        for (yy = (byte)(C.ps.y - 1); yy <= C.ps.y + 1; yy++)
                                        {
                                            for (zz = C.ps.z - 1; zz <= C.ps.z + 1; zz++)
                                            {
                                                if ((Block)GetBlock(xx, yy, zz).type == Block.Water)
                                                {
                                                    hasWater = true;
                                                }
                                            }
                                        }
                                    }

                                    if (!hasWater) physicsListCheck.Remove(C);
                                    C.b.metadata++;
                                    C.extraProperties[0] = rand.Next(2, 10);
                                }
                                break;

                            case Block.Sapling:
                            // 0x0 is a freshly planted sapling. The data value is incremented at random intervals.
                            // When it becomes 15, a new tree is created in its place. 
                                if (C.b.metadata == 0x15)
                                {
                                    // TODO: TrySpawnTree
                                    physicsListCheck.Remove(C);
                                    break;
                                }
                                else
                                {
                                    //TODO: Check Block has sunlight
                                    C.b.metadata++;
                                    C.extraProperties[0] = rand.Next(2, 10);
                                }
                                break;

                            case Block.Cactus:
                            // 0x0 is a freshly planted cactus. The data value is incremented at random intervals.
                            // When it becomes 15, a new cactus block is created on top as long as the total height does not exceed 3. 
                                if (C.b.metadata == 0x15)
                                {
                                    Logger.Log("Processing Cacuts: " + C.ps.x + " " + (byte)(C.ps.y + 1) + " " + C.ps.z, LogType.Debug);

                                    if ((Block)GetBlock(C.ps.x, (byte)(C.ps.y - 2), C.ps.z).type != Block.Cactus)
                                    {
                                        if ((Block)GetBlock(C.ps.x, (byte)(C.ps.y + 1), C.ps.z).type == Block.Air)
                                        {
                                            ChangeBlock(new ChunkBlock(C.b.type, 0, C.b.lighting), C.ps.x, (byte)(C.ps.y + 1), C.ps.z, null);
                                        }
                                    }

                                    physicsListCheck.Remove(C);
                                    break;
                                }
                                else
                                {
                                    C.b.metadata++;
                                    C.extraProperties[0] = rand.Next(2, 10);
                                }
                                break;

                            #endregion

                            default:
                                physicsListCheck.Remove(C);
                                break;
                        }
                    }
                });

                physicsListCheck.RemoveAll(Check => Check.time == 300);  //Remove all that are finished with 200 time, just a safety net currently, to catch stray monkeys.

                plastUpdate = physicsListUpdate.Count;

                physicsListUpdate.ForEach(delegate(UpdateBlock C)
                {
                    Blockchange(C.ps, C.b, C.originalType);
                });

                physicsListUpdate.Clear();
            }
        }
        public void PAddCheck(BlockPosition p, ChunkBlock b, int[] bextra, bool overRide = false)
        {
            if (!Blocks.PhysicsBlocks.Contains((Block)b.type) && !Blocks.PhysicsBlocks.Contains((Block)b.type) && !overRide) return;

            if (overRide == true)
            {
                physicsListCheck.Add(new CheckBlock(p, GetBlock(p.x, p.y, p.z), bextra));
            }
            else
            {
                if (!physicsListCheck.Exists(Check => Check.ps.x == p.x) || !physicsListCheck.Exists(Check => Check.ps.z == p.z) || !physicsListCheck.Exists(Check => Check.ps.y == p.y))
                {
                    physicsListCheck.Add(new CheckBlock(p, b, bextra));    //Adds block to list to be updated
                }
            }
        }

        public void PAddCheck(BlockPosition p, int[] bextra)
        {
            ChunkBlock b = GetBlock(p.x, p.y, p.z);

            if (!physicsListCheck.Exists(Check => Check.ps.x == p.x) || !physicsListCheck.Exists(Check => Check.ps.z == p.z) || !physicsListCheck.Exists(Check => Check.ps.y == p.y))
                {
                    physicsListCheck.Add(new CheckBlock(p, b, bextra));    //Adds block to list to be updated
                }
        }

        public void ClearPhysics()
        {
            physicsListCheck.Clear();
            physicsListUpdate.Clear();
        }
        //================================================================================================================

        public void TryFlood(int x, byte y, int z, ChunkBlock b, bool downOnly = false)
        {
            ChunkBlock cb = GetBlock(x, y, z);

            switch ((Block)cb.type)
            {
                case Block.Torch:
                case Block.RedstoneTorchOff:
                case Block.RedstoneTorchOn:
                case Block.RedstoneWire:
                case Block.RedMushroom:
                case Block.Rails:
                case Block.BrownMushroom:
                case Block.RedFlower:
                case Block.YellowFlower:
                case Block.SnowSurface:
                case Block.Sapling:
                case Block.Fire:
                case Block.Air:
                    if (!downOnly)
                    {
                        //b.metadata++;
                        ChangeBlock(b, x, y, z, null);
                        //physicsListUpdate.Add(new UpdateBlock(new BlockPosition(x, y, z), b, cb.type, new int[8] { b.metadata, 0, 0, 0, 0, 0, 0, 0 }));
                        //PAddCheck(new BlockPosition(x, y, z), cb, new int[8] { b.metadata, 0, 0, 0, 0, 0, 0, 0 });
                    }
                    else
                    {
                        b.metadata = 0x08;
                        physicsListUpdate.Add(new UpdateBlock(new BlockPosition(x, y, z), b, cb.type, new int[8] { b.metadata, 0, 0, 0, 0, 0, 0, 0 }));
                        //PAddCheck(new BlockPosition(x, y, z), b, new int[8] { 2, 0, 0, 0, 0, 0, 0, 0 });
                    }
                    break;


                case Block.Water:
                case Block.StillWater:
                    {
                        if (cb.metadata != 0x00 && cb.metadata != 0x08)
                        {
                            //cb.metadata++;
                            ChangeBlock(b, x, y, z, null);
                            //PAddCheck(new BlockPosition(x, y, z), b, new int[8] { b.metadata, 0, 0, 0, 0, 0, 0, 0 });
                        }
                    }
                    break;

                case Block.Lava:
                case Block.StillLava:
                    {
                        if (cb.metadata != 0x00)
                        {
                            //cb.metadata++;
                            ChangeBlock(b, x, y, z, null);
                            //PAddCheck(new BlockPosition(x, y, z), b, new int[8] { b.metadata, 0, 0, 0, 0, 0, 0, 0 });
                        }
                    }
                    break;

            }

        }

        #endregion

    }
}